#include "parse_chunked_body.h"

#define IS_WHITE_SPACE(x)         ((x == ' ') || (x == '\t'))

int parse_chunked_body (http_connection_t *client_connection)
{
	/*
	 * Return values:
	 * -1: Error parsing.
	 *  0: We haven't reached the end of body.
	 *  1: We have reached the end of body.
	 */

	buffer_t *buffer = &client_connection->buffer;
	char *data;
	off_t offset;
	unsigned char c;

	data = buffer->data;
	offset = client_connection->offset;

	while (offset < buffer->used) {
		c = (unsigned char) data[offset];
		switch (client_connection->request_state) {
			case 0: /* First character of chunk size. */
				if ((c >= '0') && (c <= '9')) {
					client_connection->content_length = c - '0';
				} else if ((c >= 'a') && (c <= 'f')) {
					client_connection->content_length = c - 'a' + 10;
				} else if ((c >= 'A') && (c <= 'F')) {
					client_connection->content_length = c - 'A' + 10;
				} else {
					return -1;
				}

				client_connection->request_state = 1; /* Reading chunk size. */
				offset++;
				break;
			case 1: /* Reading chunk size. */
				if ((c >= '0') && (c <= '9')) {
					client_connection->content_length = (client_connection->content_length * 16) + (c - '0');
				} else if ((c >= 'a') && (c <= 'f')) {
					client_connection->content_length = (client_connection->content_length * 16) + (c - 'a' + 10);
				} else if ((c >= 'A') && (c <= 'F')) {
					client_connection->content_length = (client_connection->content_length * 16) + (c - 'A' + 10);
				} else if (IS_WHITE_SPACE (c)) {
					client_connection->request_state = 2; /* Whitespace after chunk size. */
				} else if (c == '\r') {
					client_connection->request_state = 3; /* '\r' */
				} else if (c == '\n') {
					client_connection->request_state = 4; /* '\n' */
				} else {
					return -1;
				}

				offset++;
				break;
			case 2: /* Whitespace after chunk size. */
				if (c == '\r') {
					client_connection->request_state = 3; /* '\r' */
				} else if (c == '\n') {
					client_connection->request_state = 4; /* '\n' */
				} else if (c < ' ') {
					return -1;
				}

				offset++;
				break;
			case 3: /* '\r' */
				if (c == '\n') {
					client_connection->request_state = 4; /* '\n' */
				} else {
					return -1;
				}

				offset++;
				break;
			case 4: /* '\n' */
				/* Last chunk? */
				if (client_connection->content_length == 0) {
					client_connection->request_state = 8; /* Waiting for last CRLF. */
					offset++;
				} else {
					if (offset + client_connection->content_length > buffer->used) {
						client_connection->content_length -= (buffer->used - offset);

						client_connection->request_state = 5; /* In chunk. */
						return 0;
					} else {
						client_connection->request_state = 6; /* After chunk. */
						offset += client_connection->content_length;
					}
				}

				break;
			case 5: /* In chunk. */
				if (offset + client_connection->content_length > buffer->used) {
					client_connection->content_length -= (buffer->used - offset);
					return 0;
				} else {
					client_connection->request_state = 6; /* After chunk. */
					offset += client_connection->content_length;
				}

				break;
			case 6: /* After chunk. */
				if (c == '\r') {
					client_connection->request_state = 7; /* '\r' before chunk size. */
				} else if (c == '\n') {
					client_connection->request_state = 0; /* First character of chunk size. */
				} else {
					return -1;
				}

				offset++;
				break;
			case 7: /* '\r' before chunk size. */
				if (c == '\n') {
					client_connection->request_state = 0; /* First character of chunk size. */
				} else {
					return -1;
				}

				offset++;
				break;
			case 8: /* Waiting for last CRLF. */
				if (c == '\r') {
					client_connection->request_state = 9; /* '\r' */
				} else if (c == '\n') {
					client_connection->nCRLFs++;
					client_connection->request_state = 10; /* '\n' */
				}

				offset++;
				break;
			case 9: /* '\r' */
				if (c == '\n') {
					if (client_connection->nCRLFs == 1) {
						return 1;
					}

					client_connection->nCRLFs++;

					client_connection->request_state = 10; /* '\n' */
				} else {
					return -1;
				}

				offset++;
				break;
			case 10: /* '\n' */
				if (c == '\r') {
					client_connection->request_state = 9; /* '\r' */
				} else if (c == '\n') {
					return 1;
				} else {
					client_connection->nCRLFs = 0;
				}

				offset++;
				break;
		}
	}

	return 0;
}
